use ordered_float::NotNan;
use pyo3::class::basic::PyObjectProtocol;
use pyo3::prelude::*;
use serde::Deserialize;


type Map = std::collections::BTreeMap<NotNan<f64>, f64>;

#[derive(Deserialize)]
pub struct Record<RecordData> {
    pub timestamp: i64,
    pub data: RecordData,
}

#[pyclass]
pub struct Trade {
    #[pyo3(get)]
    pub timestamp: i64,
    #[pyo3(get)]
    pub price: f64,
    #[pyo3(get)]
    pub qty: f64,
    #[pyo3(get)]
    pub side: String,  // rust enum => python enum is not implemeneted
}

#[pyclass]
pub struct PriceQty {
    price: NotNan<f64>,
    qty: f64,
}

#[pymethods]
impl PriceQty {
    #[getter]
    fn price(&self) -> f64 {
        self.price.into_inner()
    }
    #[getter]
    fn qty(&self) -> f64 {
        self.qty
    }
}

#[pyproto]
impl PyObjectProtocol for PriceQty {
    fn __repr__(&self) -> PyResult<String> {
        Ok(format!("PriceQty(price={}, qty={})", self.price, self.qty))
    }
}

pub type PriceQtyList = Vec<(NotNan<f64>, f64)>;

#[derive(Deserialize)]
pub struct AsksBids {
    #[serde(default)]
    pub asks: PriceQtyList,
    #[serde(default)]
    pub bids: PriceQtyList,
}

#[pyclass]
pub struct BookBuilder {
    pub timestamp: Option<i64>,
    pub bids: Map,
    pub asks: Map,
}

impl BookBuilder {
    pub fn new() -> BookBuilder {
        BookBuilder {
            timestamp: None,
            bids: Map::new(),
            asks: Map::new(),
        }
    }
    pub fn update_timestamp(&mut self, timestamp: i64) {
        self.timestamp = Some(timestamp);
    }
    pub fn update_ask(&mut self, price: NotNan<f64>, qty: f64) {
        self.asks.insert(price, qty);
    }
    pub fn update_bid(&mut self, price: NotNan<f64>, qty: f64) {
        self.bids.insert(price, qty);
    }
    pub fn delete_ask(&mut self, price: NotNan<f64>) {
        self.asks.remove(&price);
    }
    pub fn delete_bid(&mut self, price: NotNan<f64>) {
        self.bids.remove(&price);
    }
    pub fn on_snapshot(
        &mut self,
        timestamp: i64,
        asks: &Vec<(NotNan<f64>, f64)>,
        bids: &Vec<(NotNan<f64>, f64)>,
    ) {
        self.clear();
        self.on_diff(timestamp, asks, bids);
    }
    pub fn prune_crossing_with_ask(&mut self, ask_price: NotNan<f64>) {
        let mut to_remove: Vec<NotNan<f64>> = Vec::new();
        for (key, _) in self.bids.range(ask_price..) {
            to_remove.push(*key);
        }
        for key in to_remove {
            self.bids.remove(&key);
        }
    }
    pub fn prune_crossing_with_bid(&mut self, bid_price: NotNan<f64>) {
        let mut to_remove: Vec<NotNan<f64>> = Vec::new();
        for (key, _) in self.asks.range(..=bid_price) {
            to_remove.push(*key);
        }
        for key in to_remove {
            self.asks.remove(&key);
        }
    }
    pub fn on_diff(
        &mut self,
        timestamp: i64,
        asks: &Vec<(NotNan<f64>, f64)>,
        bids: &Vec<(NotNan<f64>, f64)>,
    ) {
        let mut insert_min_ask : f64 = 1.0e10;
        let mut insert_max_bid : f64 = 0.0;

        self.timestamp = Some(timestamp);
        for ask in asks {
            let ask0 : NotNan<f64> = NotNan::new((*ask.0 * 1e8).round() / 1e8).unwrap();
            if ask.1 == 0.0 {
                self.asks.remove(&ask0);
            } else {
                self.asks.insert(ask0, ask.1);
                if insert_min_ask > *ask0 {
                    insert_min_ask = *ask0;
                }
            }
        }
        for bid in bids {
            let bid0 : NotNan<f64> = NotNan::new((*bid.0 * 1e8).round() / 1e8).unwrap();
            if bid.1 == 0.0 {
                self.bids.remove(&bid0);
            } else {
                self.bids.insert(bid0, bid.1);
                if insert_max_bid < *bid0 {
                    insert_max_bid = *bid0;
                }
            }
        }

        if insert_min_ask < 1.0e10 {
            self.prune_crossing_with_ask(NotNan::new(insert_min_ask).unwrap());
        }

        if insert_max_bid > 0.0 {
            self.prune_crossing_with_bid(NotNan::new(insert_max_bid).unwrap());
        }
    }
    pub fn clear(&mut self) {
        self.asks.clear();
        self.bids.clear();
    }
}

#[pymethods]
impl BookBuilder {
    #[new]
    fn new_python(obj: &PyRawObject) {
        obj.init(BookBuilder::new());
    }
    #[getter]
    fn timestamp(&self) -> Option<i64> {
        self.timestamp
    }
    fn has_ask(&self) -> bool {
        self.asks.len() > 0
    }
    fn has_bid(&self) -> bool {
        self.bids.len() > 0
    }
    fn ask0(&self) -> Option<PriceQty> {
        self.asks
            .iter()
            .next()
            .map(|(k, v)| PriceQty { price: *k, qty: *v })
    }
    fn bid0(&self) -> Option<PriceQty> {
        self.bids
            .iter()
            .next_back()
            .map(|(k, v)| PriceQty { price: *k, qty: *v })
    }
    fn get_ask_array(
        &mut self,
        n: usize) -> Option<Vec<(f64, f64)>> {
        let res = self.asks
            .iter()
            .take(n)
            .map(|(p, q)| (f64::from(*p), *q))
            .collect::<Vec<_>>();
        Some(res)
    }
    fn get_bid_array(
        &mut self,
        n: usize) -> Option<Vec<(f64, f64)>> {
        let res = self.bids
            .iter()
            .rev()
            .take(n)
            .map(|(p, q)| (f64::from(*p), *q))
            .collect::<Vec<_>>();
        Some(res)
    }
}

// use failure::Error;
// use flate2::bufread::GzDecoder;

// #[pyclass]
// pub struct ReadContext {
//     file_gz: GzDecoder<std::io::BufReader<std::fs::File>>,
//     length: i64,
//     offset: i64,
//     buf: Vec<u8>,
// }

// impl ReadContext {
//     pub fn new(path: &str) -> Result<ReadContext, Error> {
//         let fptr = std::fs::File::open(path)?;
//         let buf = std::io::BufReader::new(fptr);
//         let file_gz = flate2::bufread::GzDecoder::new(buf);

//         Ok(ReadContext {
//             file_gz: file_gz,
//             length: 0,
//             offset: 0,
//             buf: vec![0u8; 1024],
//         })
//     }
// }
